﻿using System.IO.Ports;
using System.Web;
using library.core;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using library;
using System.Collections.Specialized;
using System.Windows.Forms;
using windows_desktop.Properties;
using System.Drawing.Imaging;
using Docuverse.Identicon;
using System.Drawing;

namespace windows_desktop
{

    public class WebServer : IDisposable
    {
        static HttpListener server;

        public delegate void DraggingHandler(bool dragging, string dragId, string userAddress);

        static public event DraggingHandler OnDragging;

        static p2pResult searchs = new p2pResult();

        static Cache<FileDownloadObject> downloads = new Cache<FileDownloadObject>(5000);

        #region Thread Refresh

        static Thread thread;

        static bool stop = false;

        internal static IDisposable Start()
        {
            if (thread == null)
            {
                thread = new Thread(Configure);

                thread.Start();
            }


            return new WebServer();
        }

        public void Dispose()
        {
            stop = true;

            server.Stop();
        }

        static void Configure()
        {
            Client.OnSearchReturn += Client_onSearchReturn;

            Client.OnFileDownload += Client_OnFileDownload;

            server = new HttpListener();

            server.Prefixes.Add("http://*:" + Program.WebPort + "/");

            server.Start();

            ThreadReceive();
        }

        static void ThreadReceive()
        {
            while (!stop)
            {
                try
                {
                    if (server.IsListening)
                        ThreadPool.QueueUserWorkItem(ProcessReceive, server.GetContext());
                    //ProcessReceive(server.GetContext());
                }
                catch { }
            }
        }



        #endregion

        static void ProcessReceive(object o)
        {
            var context = (HttpListenerContext)o;

            var response = context.Response;

            //context.Response.AddHeader("Access-Control-Allow-Origin", "*");

            var baseAddress = GetSegment(context, 1);

            if (baseAddress == null || Utils.FromBase64String(baseAddress) == null)
            {
                CloseResponse(context);

                return;
            }

            var command = string.Empty;

            if (baseAddress == Program.webHome)
                command = GetSegment(context, 2);
            
            switch (command)
            {
                default:

                    ProcessGet(context);

                    break;

                case WebCommands.Search:

                    ProcessSearch(context);

                    break;

                case WebCommands.CreateUserAvatar:

                    ProcessCreateUserAvatar(context);

                    break;

                case WebCommands.FriendKey:

                    var key = Client.GetWelcomeKey();

                    CloseResponse(context, key);

                    break;

                case WebCommands.AddPeer:

                    ProcessAddPeer(context, "");

                    break;

                case WebCommands.Context:

                    ProcessContext(context);

                    break;

                case WebCommands.Post:

                    ProcessPost(context);

                    break;

                case WebCommands.FileUpload:

                    //var files = JsonConvert.DeserializeObject(HttpUtility.UrlDecode(requestParams));

                    break;

                case WebCommands.DragOver:
                case WebCommands.DragLeave:

                    ProcessDrag(context, "");

                    break;
            }
        }

        static void ProcessContext(HttpListenerContext context)
        {
            if (context.Request.Url.Segments.Length < 2)
            {
                CloseResponse(context);

                return;
            }

            var contextId = context.Request.Url.Segments[1];

            ValueHits<p2pPost> result = null;

            var byteContextId = Utils.FromBase64String(contextId);

            byte[] buffer;

            string json = string.Empty;

            lock (searchs)
                result = searchs.Get(byteContextId);

            if (result != null && result.Any())
            {
                json = JsonConvert.SerializeObject(result);

                buffer = context.Request.ContentEncoding.GetBytes(json);

                context.Response.OutputStream.Write(buffer, 0, buffer.Length);
            }

            context.Response.Close();

            if (result != null)
                result.ForEach(x =>
                {

                    if (searchs.Sent(x.HitValue.Address, byteContextId) == 0)
                        processSearch(contextId, x.HitValue.Base64Address);

                    if (x.HitValue.TargetBase64Address != null)
                        if (searchs.Sent(x.HitValue.targetAddress, byteContextId) == 0)
                            processSearch(contextId, x.HitValue.TargetBase64Address);

                    if (Utils.FromBase64String(x.HitValue.Content) != null)
                        if (searchs.Sent(Utils.FromBase64String(x.HitValue.Content), byteContextId) == 0)
                            processSearch(contextId, x.HitValue.Content);

                });
        }

        static void ProcessSearch(HttpListenerContext context)
        {
            if (context.Request.Url.Segments.Length < 3)
            {
                CloseResponse(context);

                return;
            }

            context.Response.Close();

            var contextId = context.Request.Url.Segments[1];

            var term = HttpUtility.UrlDecode(context.Request.Url.Segments[2]);

            processSearch(contextId, term);
        }

        static bool ProcessCache(HttpListenerContext context)
        {
            var path = GetSegment(context, 1);

            path = Program.webCache + context.Request.RawUrl;

            if (Directory.Exists(path))
                path = Path.Combine(path, "index.html");

            if (File.Exists(path))
            {
                byte[] buffer = File.ReadAllBytes(path);

                context.Response.ContentType = Utils.GetMimeType(path); // "text/html";

                context.Response.OutputStream.Write(buffer, 0, buffer.Length);

                context.Response.Close();

                return true;
            }

            return false;
        }

        static void ProcessGet(HttpListenerContext context)
        {
            if (ProcessCache(context))
                return;

            if (!Client.AnyPeer())
            {
                context.Response.ContentType = "html";

                CloseResponse(context, Resources.welcome);

                return;
            }

            //downloads.Add(new FileDownloadObject(address));

           // Client.Download(address, address);

            #region old 

            //var address = Utils.FromBase64String(command);

            //if (address != null)
            //{
            //    filename = Path.Combine(Program.webCache, command);

            //    if (path != "index.html" || context.Request.QueryString["type"] != null)
            //    {
            //        if (context.Request.QueryString["type"] == "download")
            //        {
            //            filename = context.Request.QueryString["name"];

            //            filename = Program.UIHelper.SaveAs(filename);

            //            if (!string.IsNullOrEmpty(filename))
            //                Client.Download(command, filename);

            //            context.Response.Close();

            //            return;
            //        }
            //        else if (commands.Length > 1 || context.Request.QueryString["type"] == "web")
            //        {
            //            if (!string.IsNullOrEmpty(filename))
            //            {
            //                Client.Download(command, Path.Combine(Program.webCache, command), path);

            //                getResults.Add(new GetReturnObject(command, context, path));
            //            }

            //            return;
            //        }

            //        context.Response.ContentType = context.Request.QueryString["type"];
            //    }
            //    else
            //        context.Response.ContentType = "application/octet-stream";

            //    if (context.Request.Headers["Range"] != null || context.Request.Headers["If-None-Match"] != null)
            //        ProcessSeek(context, command);

            //    byte[] buffer = Client.GetPost(address);

            //    if (buffer != null)
            //    {
            //        context.Response.OutputStream.Write(buffer, 0, buffer.Length);
            //    }
            //    else
            //    {
            //        getResults.Add(new GetReturnObject(command, context));

            //        Client.Download(command, filename);

            //        return;
            //    }

            //    context.Response.Close();
            //}

            #endregion
        }

        private static void ProcessSeek(HttpListenerContext context, string base64Address)
        {
            CacheItem<FileDownloadObject> result = null;

            lock (downloads)
                result = downloads.FirstOrDefault(x => x.CachedValue.Address.Equals(base64Address));

            if (result == null)
                return;

            Stream file = result.CachedValue.FileStream;

            context.Response.StatusCode = 206;

            long range1 = 0;

            long range2 = file.Length - 1;

            try
            {
                var ranges = context.Request.Headers["Range"].Substring(6).Split('-');

                range1 = long.Parse(ranges[0]);

                range2 = long.Parse(ranges[1]);
            }
            catch { }

            context.Response.AddHeader("Content-Length", (range2 - range1).ToString());

            context.Response.AddHeader("Content-Range", string.Format("bytes {0}-{1}/{2}", range1, range2, file.Length)); ;

            long responseSize = (range2 + 1) - range1;

            context.Response.ContentLength64 = responseSize;

            file.Seek(range1, 0);

            long bufferSize = Math.Min(Program.MaxNonRangeDownloadSize, responseSize);

            var buffer = new byte[bufferSize];

            var read = 0;

            try
            {
                while ((read = file.Read(buffer, 0, (int)bufferSize)) > 1)
                {
                    context.Response.OutputStream.Write(buffer, 0, read);

                    context.Response.OutputStream.Flush();

                    result.Reset();
                }

                context.Response.Close();
            }
            catch { }
        }

        private static void processSearch(string contextId, string term)
        {
            var bTerm = Utils.FromBase64String(term);

            var bContextId = Utils.FromBase64String(contextId);

            if (bTerm != null)
            {
                searchs.Reset(bContextId);

                Client.Search(term);

                return;
            }

            term = term.ToUpper();

            var terms = term.Split(' ');

            searchs.AddTerm(bContextId, term);

            foreach (var t in terms)
                Client.Search(t);

            //var address = Utils.FromBase64String(term);

            //if (address != null)
            //    Client.Get(term);
        }


        private static void ProcessCreateUserAvatar(HttpListenerContext context)
        {
            string address = "";
            var renderer = new IdenticonRenderer();

            var bAddress = Utils.FromBase64String(address);

            var i = BitConverter.ToInt32(bAddress, 0);

            using (Bitmap bmp = renderer.Render(i, 128))
            {
                var path = Path.Combine(Program.webCache, address + ".jpg");

                using (var ms = new MemoryStream())
                {
                    bmp.Save(ms, ImageFormat.Jpeg);

                    var data = new byte[ms.Length];

                    ms.Seek(0, 0);

                    ms.Read(data, 0, data.Length);

                    var post = p2pPost.Create(data, null, bAddress);

                    p2pPost.Create(p2pFile.FILE_TYPE_IMAGE, null, post.Address);

                    p2pPost.Create(post.Base64Address, null, bAddress);
                }
            }

            context.Response.Close();
        }

        private static void ProcessAddPeer(HttpListenerContext context, string key)
        {
            Client.GetPeer(key);

            context.Response.Redirect(Program.webHome + "/index.html");

            context.Response.Close();
        }

        static void ProcessDrag(HttpListenerContext context, string command, params string[] args)
        {
            if (args.Length < 2)
                CloseResponse(context);

            if (OnDragging != null)
                OnDragging(command == WebCommands.DragOver, args[0], args[1]);

            context.Response.Close();
        }

        static void ProcessPost(HttpListenerContext context, params string[] args)
        {
            if (args.Length < 2)
            {
                CloseResponse(context);

                return;
            }

            var text = HttpUtility.UrlDecode(args[0].Replace("%2B", "+"));

            var address = Client.Post(text, null, args[1]);

            CloseResponse(context, Utils.ToBase64String(address));
        }

        static void Client_onSearchReturn(string term, ValueHits<p2pPost> posts)
        {
            List<CacheItem<p2pResultItem>> items = null;

            lock (searchs)
            {
                items = searchs.AddResults(term, posts);
            }

        }

        static void Client_OnFileDownload(byte[] address, string filename, string speficFilename = null)
        {
            //CacheItem<GetReturnObject> result;

            //var base64Address = Utils.ToBase64String(address);

            //lock (getResults)
            //    result = getResults.FirstOrDefault(x => x.CachedValue.Address.Equals(base64Address));

            //if (result == null)
            //    return;

            ////var filename = Path.Combine(@"C:\arede\windows-desktop\bin\Debug\www\", result.CachedValue.Address);

            //if (!string.IsNullOrEmpty(speficFilename))
            //    filename = Path.Combine(filename, speficFilename);

            //try
            //{
            //    FileInfo file = new FileInfo(filename);

            //    if (file.Length <= Program.MaxNonRangeDownloadSize)
            //    {
            //        var buffer = File.ReadAllBytes(filename);

            //        if (result.CachedValue.Context.Response.OutputStream.CanWrite)
            //            result.CachedValue.Context.Response.OutputStream.Write(buffer, 0, buffer.Length);

            //        result.CachedValue.Context.Response.Close();
            //    }
            //    else
            //    {
            //        var seek = new FileDownloadObject(base64Address, new FileStream(filename, FileMode.Open, FileAccess.Read));

            //        downloads.Add(seek);

            //        var read = 0;

            //        var buffer = new byte[Program.MaxNonRangeDownloadSize];

            //        while ((read = seek.FileStream.Read(buffer, 0, buffer.Length)) > 0)
            //        {
            //            result.CachedValue.Context.Response.OutputStream.Write(buffer, 0, read);

            //            result.CachedValue.Context.Response.OutputStream.Flush();
            //        }

            //        result.CachedValue.Context.Response.Close();

            //    }
            //}
            //catch { }

            //result.Expire();
        }

        static void CloseResponse(HttpListenerContext context, string result = null)
        {
            if (result == null)
            {
                context.Response.RedirectLocation = Program.webHome + "/index.html";

                context.Response.Redirect(Program.webHome + "/index.html");

                //context.Response.StatusCode = (int)HttpStatusCode.Ambiguous;
            }
            else
            {

                var json = JsonConvert.SerializeObject(result);

                var buffer = context.Request.ContentEncoding.GetBytes(json);

                context.Response.OutputStream.Write(buffer, 0, buffer.Length);
            }

            context.Response.Close();
        }

        static string GetSegment(HttpListenerContext context, int segment)
        {
            if (context.Request.Url.Segments.Length <= segment)
                return null;
            
            return removeLastSlash(context.Request.Url.Segments[segment]);
        }

        static string removeLastSlash(string value)
        {
            if (value[value.Length - 1] == '/')
                return value.Substring(0, value.Length - 1);

            return value;
        }

        class WebCommands
        {
            internal const string Search = "search";

            internal const string Context = "context";

            internal const string DragOver = "dragover";

            internal const string DragLeave = "dragleave";

            internal const string FileUpload = "fileUpload";

            internal const string Post = "post";

            internal const string AddPeer = "addPeer";

            internal const string FriendDownload = "friendDownload";

            internal const string FriendKey = "friendKey";

            internal const string CreateUserAvatar = "createUserAvatar";
        }

        class FileDownloadObject : IDisposable
        {
            internal string Address;

            internal Stream FileStream;

            internal FileDownloadObject(string base64Address, Stream fileStream = null)
            {
                Address = base64Address;

                FileStream = fileStream;
            }

            public void Dispose()
            {
                FileStream.Close();
            }
        }
    }
}

